package io.github.eric.urpc.server;

import io.github.eric.urpc.core.config.AbstractConfig;
import io.github.eric.urpc.core.config.Config;
import io.github.eric.urpc.core.conn.ConnectionListener;
import io.github.eric.urpc.core.rpc.RpcController;
import io.github.eric.urpc.core.thread.NamePrefixThreadFactory;
import io.github.eric.urpc.core.thread.OrderedThreadPoolExecutor;
import io.netty.handler.logging.LogLevel;
import io.netty.util.HashedWheelTimer;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public interface ServerConfig extends Config {
    int getListenPort();
    int getHeartBeatTimeout();

    class Builder {
        private static final String PROP_LISTEN_PORT = "urpc.server.listenPort";
        private static final String PROP_RPC_EXECUTOR_CORE_SIZE = "urpc.server.rpcExecutorCoreSize";
        private static final String PROP_RPC_EXECUTOR_QUEUE_SIZE = "urpc.server.rpcExecutorQueueSize";
        private static final String PROP_HEART_BEAT_TIMEOUT = "urpc.server.heartBeatTimeout";

        private String applicationName;
        private String applicationId;

        private int socketBacklog;
        private int socketRecvBufSize;
        private int socketSendBufSize;
        private int frameMaxLength;

        private int listenPort;

        private List<RpcController> rpcControllers;

        private ExecutorService rpcExecutor;
        private int rpcExecutorThreadSize;
        private int rpcExecutorQueueSize;

        private int heartBeatTimeout;

        private LogLevel nettyLoggingLevel;

        private int readTimeout;

        private boolean ordered;

        private List<ConnectionListener> connectionListeners;

        public Builder() {
            int cpuCore = Runtime.getRuntime().availableProcessors();

            this.applicationId = System.getProperty(AbstractConfig.PROP_APPLICATION_ID, AbstractConfig.DEFAULT_CURRENT_APPLICATION_ID);
            this.socketBacklog = Integer.getInteger(AbstractConfig.PROP_SOCKET_BACKLOG, AbstractConfig.DEFAULT_SOCKET_BACKLOG);
            this.socketRecvBufSize = Integer.getInteger(AbstractConfig.PROP_SOCKET_RECV_BUF_SIZE, AbstractConfig.DEFAULT_SOCKET_RECV_BUF_SIZE);
            this.socketSendBufSize = Integer.getInteger(AbstractConfig.PROP_SOCKET_SEND_BUF_SIZE, AbstractConfig.DEFAULT_SOCKET_SEND_BUF_SIZE);
            this.frameMaxLength = Integer.getInteger(AbstractConfig.PROP_FRAME_MAX_LENGTH, AbstractConfig.DEFAULT_FRAME_MAX_LENGTH);
            this.listenPort = Integer.getInteger(PROP_LISTEN_PORT, 52520);
            this.rpcExecutorThreadSize = Integer.getInteger(PROP_RPC_EXECUTOR_CORE_SIZE, 2 * cpuCore);
            this.rpcExecutorQueueSize = Integer.getInteger(PROP_RPC_EXECUTOR_QUEUE_SIZE, 128);
            this.heartBeatTimeout = Integer.getInteger(PROP_HEART_BEAT_TIMEOUT, 60);
            try {
                this.nettyLoggingLevel = LogLevel.valueOf(System.getProperty(AbstractConfig.PROP_NETTY_LOGGING_LEVEL));
            } catch (Exception ignore) {
                this.nettyLoggingLevel = null;
            }
            this.readTimeout = Integer.getInteger(AbstractConfig.PROP_READ_TIMEOUT, AbstractConfig.DEFAULT_READ_TIMEOUT);
            this.ordered = Boolean.getBoolean(AbstractConfig.PROP_ORDERED);
        }

        public Builder listenPort(int bindPort) {
            this.listenPort = bindPort;
            return this;
        }

        public Builder socketBacklog(int socketBacklog) {
            this.socketBacklog = socketBacklog;
            return this;
        }

        public Builder socketRecvBufSize(int socketRecvBufSize) {
            this.socketRecvBufSize = socketRecvBufSize;
            return this;
        }

        public Builder socketSendBufSize(int socketSendBufSize) {
            this.socketSendBufSize = socketSendBufSize;
            return this;
        }

        public Builder frameMaxLength(int frameMaxLength) {
            this.frameMaxLength = frameMaxLength;
            return this;
        }

        public Builder rpcControllers(List<RpcController> rpcControllers) {
            this.rpcControllers = rpcControllers;
            return this;
        }

        public Builder rpcExecutor(ExecutorService rpcExecutor) {
            this.rpcExecutor = rpcExecutor;
            return this;
        }

        public Builder rpcExecutorThreadSize(int rpcExecutorThreadSize) {
            this.rpcExecutorThreadSize = rpcExecutorThreadSize;
            return this;
        }

        public Builder executorQueueSize(int executorQueueSize) {
            this.rpcExecutorQueueSize = executorQueueSize;
            return this;
        }

        /**
         * @param heartBeatTimeout [unit: second]
         * @return
         */
        public Builder heartBeatTimeout(int heartBeatTimeout) {
            this.heartBeatTimeout = heartBeatTimeout;
            return this;
        }

        public Builder nettyLoggingLevel(LogLevel nettyLoggingLevel) {
            this.nettyLoggingLevel = nettyLoggingLevel;
            return this;
        }

        public Builder readTimeout(int readTimeout) {
            this.readTimeout = readTimeout;
            return this;
        }

        public Builder applicationName(String applicationName) {
            this.applicationName = applicationName;
            return this;
        }

        public Builder applicationId(String applicationId) {
            this.applicationId = applicationId;
            return this;
        }

        public Builder ordered(boolean ordered) {
            this.ordered = ordered;
            return this;
        }

        public Builder connectionListeners(List<ConnectionListener> connectionListeners) {
            this.connectionListeners = connectionListeners;
            return this;
        }

        public ServerConfig build() {
            if (this.applicationName == null || this.applicationName.isEmpty()) {
                throw new IllegalArgumentException("prop '" + AbstractConfig.PROP_APPLICATION_NAME + "' is not present");
            }
            if (this.rpcExecutor == null) {
                if (ordered) {
                    this.rpcExecutor = new OrderedThreadPoolExecutor(this.rpcExecutorThreadSize, this.rpcExecutorQueueSize,
                            "urpc-server-exec");
                } else {
                    this.rpcExecutor = new ThreadPoolExecutor(this.rpcExecutorThreadSize, this.rpcExecutorThreadSize,
                            0L, TimeUnit.SECONDS,
                            new LinkedBlockingDeque<>(this.rpcExecutorQueueSize),
                            new NamePrefixThreadFactory("urpc-server-exec"));
                }
            }

            ServerContext serverContext = new ServerContext(rpcExecutor);
            serverContext.setApplicationName(applicationName);
            serverContext.setApplicationId(applicationId);
            serverContext.setSocketBacklog(socketBacklog);
            serverContext.setSocketRecvBufSize(socketRecvBufSize);
            serverContext.setSocketSendBufSize(socketSendBufSize);
            serverContext.setFrameMaxLength(frameMaxLength);
            serverContext.setListenPort(listenPort);
            serverContext.addRpcControllers(rpcControllers);
            serverContext.addConnectionListeners(connectionListeners);
            serverContext.setHeartBeatTimeout(heartBeatTimeout);
            serverContext.setNettyLoggingLevel(nettyLoggingLevel);
            serverContext.setReadTimeout(readTimeout);
            serverContext.setHashedWheelTimer(new HashedWheelTimer(new NamePrefixThreadFactory("urpc-server-hashedWheelTimer")));
            return serverContext;
        }
    }
}
